﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Collections;

namespace NETXPF.Library
{
	/// <summary>
	/// Implements helper methods for parallel execution (similar to Parallel framework in .NET 4.0)
	/// 
	/// HEAVILY, HEAVILY optimized. Do not attempt to optimize further because it's futile! Futile I tells ya!
	/// </summary>
	public static class Parallel
	{
		public static int NumberOfParallelTasks { get; set; }

		enum ThreadType
		{
			For, ForEach, Background
		}

		class ThreadInvokeParam<T>
		{
			public IEnumerator<T> Enumerator {get;set;}
			public Action<T> ActionForEach {get;set;}
			public T Item {get;set;}
			public object SyncRoot {get;set;}
			public int Index {get;set;}
			public int[] Offsets { get; set; }
			public ThreadType Type { get; set; }
			public Action<int> ActionFor { get; set; }
            public Action<T> ActionBackground { get; set; }
		}

		static Parallel()
		{
			NumberOfParallelTasks = Environment.ProcessorCount * 2;
		}

        public static Thread Background<T>(Action<T> action, T param)
        {
            Thread t = new Thread(new ParameterizedThreadStart(ThreadAction<T>));
            t.Start(new ThreadInvokeParam<T> { Type = ThreadType.Background, ActionBackground = action, Item = param });
            return t;
        }

        public static Thread Background(Action<object> action)
        {
            return Background<object>(action, null);
        }

		public static void ForEach<T>(IEnumerable<T> enumerable, Action<T> action)
		{
			var syncRoot = new object();

			if (enumerable == null) return;

			var enumerator = enumerable.GetEnumerator();
			enumerator.Reset();

			var seedItemArray = new T[NumberOfParallelTasks];
			var threadList = new List<Thread>();

			for (int i = 0; i < NumberOfParallelTasks; i++)
			{
				bool moveNext;

				lock (syncRoot)
				{
					moveNext = enumerator.MoveNext();
					if (!moveNext) continue;
					seedItemArray[i] = enumerator.Current;
				}

				if (moveNext)
				{
					Thread t = new Thread(new ParameterizedThreadStart(ThreadAction<T>));
					t.Start(new ThreadInvokeParam<T> { Enumerator = enumerator, ActionForEach = action, Index = i,
						Item = seedItemArray[i], SyncRoot = syncRoot, Type = ThreadType.ForEach });
					threadList.Add(t);
				}
			}

			foreach (var thread in threadList)
			{
				thread.Join();
			}
		}

		static void ThreadAction<T>(object param)
		{
			ThreadInvokeParam<T> p = (ThreadInvokeParam<T>)param;
			if (p.Type == ThreadType.ForEach)
			{
				InvokeAction<T>(p.Enumerator, p.ActionForEach, p.Item, p.SyncRoot, p.Index);
			}
			else if (p.Type == ThreadType.For)
			{
				InvokeActionFor(p.Offsets[0], p.Offsets[1], p.ActionFor, p.Index);
			}
            else if (p.Type == ThreadType.Background)
            {
                p.ActionBackground(p.Item);
            }
		}

		static void InvokeAction<T>(IEnumerator<T> enumerator, Action<T> action,
				T item, object syncRoot, int i)
		{
			if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
				Thread.CurrentThread.Name =
			String.Format("Parallel.ForEach Worker Thread No:{0}", i);

			bool moveNext = true;

			while (moveNext)
			{
				action.Invoke(item);

				lock (syncRoot)
				{
					moveNext = enumerator.MoveNext();
					if (!moveNext) break;
					item = enumerator.Current;
				}
			}
		}

		static void InvokeActionFor(int start, int end, Action<int> action, int i)
		{
			if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
				Thread.CurrentThread.Name = String.Format("Parallel.For Worker Thread No:{0}", i);

			for (int k = start; k < end; k++)
			{
				action.Invoke(k);
			}
		}
		
		public static void For(int start, int end, Action<int> action)
		{
			var seedItemArray = new int[NumberOfParallelTasks];
			var threadList = new List<Thread>();

			int itemCnt = (int)Math.Ceiling((double)(end - start) / (double)NumberOfParallelTasks);
			
			for (int i = 0; i < NumberOfParallelTasks; i++)
			{
				Thread t = new Thread(new ParameterizedThreadStart(ThreadAction<int>));
				t.Start(new ThreadInvokeParam<int> { ActionFor = action, Index = i, Offsets = new int[] { start, Math.Min(start + itemCnt, end) },
					Type = ThreadType.For });
				threadList.Add(t);
				start = start + itemCnt;
			}

			foreach (var t in threadList)
			{
				t.Join();
			}
		}
	}
}